﻿#region License and Copyright Notice

//XOML: XML based object persistence framework

//Copyright (c) 2008 Ananth B.
//All rights reserved.

//The contents of this file are made available under the terms of the
//Eclipse Public License v1.0 (the "License") which accompanies this
//distribution, and is available at the following URL:
//http://www.opensource.org/licenses/eclipse-1.0.php

//Software distributed under the License is distributed on an "AS IS" basis,
//WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
//the specific language governing rights and limitations under the License.

//By using this software in any fashion, you are agreeing to be bound by the
//terms of the License.

#endregion

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;

namespace Xoml
{
    public static class MarkupExtensionParser
    {
        private static readonly Regex _identifier = new Regex(@"^[a-zA-Z_]\w*"); // Begin with a letter, follow with digit, character or _

        private static bool IsIdentifier(string value)
        {
            return _identifier.Match(value).Success;
        }

        public static MarkupExtensionNode Parse(StringTokenizer tokenizer)
        {
            string prefix = string.Empty;
            string extensionName = string.Empty;
            Dictionary<string, Node> values = new Dictionary<string, Node>();

            Token t = tokenizer.Next();

            if (t.Kind != TokenKind.Word)
                throw new InvalidXomlException("Invalid markup extension syntax"); // We found something other than a word right after a {

            if (!IsIdentifier(t.Value))
                throw new InvalidXomlException(string.Format(CultureInfo.InvariantCulture, "Expected identifier, found {0}", t.Value)); // Not an identifier

            if (tokenizer.Peek().Value == ":") // Do we have a : after this token?
            {
                prefix = t.Value; // This is a prefix
                tokenizer.Next(); // Consume the :
                t = tokenizer.Next();
                if (t.Kind != TokenKind.Word || !IsIdentifier(t.Value))
                    throw new InvalidXomlException(string.Format(CultureInfo.InvariantCulture, "Expected markup extension name, found {0}", t.Value)); // Not an identifier
                extensionName = t.Value;
            }
            else
                extensionName = t.Value;

            int index = 0;
            string name = string.Empty;

            while (true)
            {
                t = tokenizer.Next();
                switch (t.Kind)
                {
                    case TokenKind.QuotedString:
                        if ((index != 0) && (tokenizer.Previous.Value != "=")) // This should either be the default argument, or the value of a <name>=<value> pair
                            throw new InvalidXomlException(string.Format(CultureInfo.InvariantCulture, "Invalid markup extension syntax, '{0}' not expected", t.Value)); 

                        values.Add(name, new LiteralNode(t.Value)); // Add the name-value pair
                        index++;
                        break;

                    case TokenKind.Word:
                        if (tokenizer.Peek().Value != "=") // If this is a name, it should have an '=' sign after it
                            throw new InvalidXomlException(string.Format(CultureInfo.InvariantCulture, "Invalid markup extension syntax, '{0}' not expected", t.Value));
                        else
                            name = t.Value;
                        index++;
                        break;

                    case TokenKind.Symbol:
                        if (t.Value == "{") // We're beginning a markup extension, read it in recursively
                            values.Add(name, Parse(tokenizer));
                        if (t.Value == "}")
                            return new MarkupExtensionNode(prefix, extensionName, values); // We're done reading this markup extension. Construct it and pass it back
                        break;
                }
            }
        }
    }
    
    public abstract class Node
    {
        public abstract string Value
        {
            get;
        }
    }

    public sealed class LiteralNode : Node
    {
        private string _value = string.Empty;

        public LiteralNode(string value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            if (value.StartsWith("'"))
                value = value.Remove(0, 1);
            if (value.EndsWith("'"))
                value = value.Remove(value.Length - 1, 1);

            _value = value;
        }

        public override string Value
        {
            get
            {
                return _value;
            }
        }
    }

    public sealed class MarkupExtensionNode : Node
    {
        private string _prefix = string.Empty;
        private string _extensionName = string.Empty;
        private Dictionary<string, Node> _arguments = null;

        public MarkupExtensionNode(string prefix, string extensionName, Dictionary<string, Node> arguments)
        {
            if (prefix == null)
                throw new ArgumentNullException("prefix");
            if (extensionName == null)
                throw new ArgumentNullException("extensionName");
            if (arguments == null)
                throw new ArgumentNullException("arguments");

            _prefix = prefix;
            _extensionName = extensionName;
            _arguments = arguments;
        }

        public string Prefix
        {
            get
            {
                return _prefix;
            }
        }

        public string ExtensionName
        {
            get
            {
                return _extensionName;
            }
        }

        public Dictionary<string, Node> Arguments
        {
            get
            {
                return _arguments;
            }
        }

        public override string Value
        {
            get
            {
                return string.Format(CultureInfo.InvariantCulture, "{0}:{1}", _prefix, _extensionName);
            }
        }
    }
}
